package server

import (
	"bytes"
	"crypto/md5"
	"encoding/base32"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/go-xorm/xorm"
	"image"
	_ "image/gif"
	"image/jpeg"
	_ "image/jpeg"
	_ "image/png"
	"math/rand"
	"net/http"
	"os"
	"strconv"
	"time"

	"github.com/dailing/levlog"
	"github.com/dgrijalva/jwt-go"
	"github.com/fogleman/gg"
	_ "github.com/go-sql-driver/mysql"
	_ "github.com/lib/pq"
	_ "golang.org/x/image/tiff"
)

const DB_NAME string = "LessionAnnotationDB"

type Image struct {
	Id             int64
	Path           string `xorm:"varchar(250) Unique"`
	Grade          int
	Patient        int64
	Examine_time   NullTime `xorm:"date"`
	Source         string
	Created        time.Time `xorm:"created"`
	VisitPermision int       `xorm:"default 10"`
	Data           string    `xorm:"-"`
}

type Person struct {
	Id         int64
	Name       string `xorm:"Unique"`
	Salt       string
	Psw        string
	Permission int
}

func PswHash(psw, salt string) string {
	levlog.Trace("HASH:", psw, ",", salt)
	sum := md5.Sum([]byte(salt + psw))
	sumstr := base32.HexEncoding.EncodeToString(sum[:])
	return sumstr
}

// A session contains a set of images to diagnose
// which comes from one eye, examined at the same time
type AnnoSession struct {
	Id                  int64
	Priority            int
	Level               int
	Finished            int
	Created             time.Time `xorm:"created"`
	Images              []Image   `xorm:"-"`
	MinAnnoNumInSession int       `xorm:"-"`
	PatientInfo         Patient   `xorm:"-"`
}

// this is the image list in a session
type ImageInSession struct {
	Id      int64
	Session int64
	Image   int64
	Created time.Time `xorm:"created"`
}

// per-session Annotation record
type SessionAnnotation struct {
	Id      int64
	Level   int
	Created time.Time `xorm:"created"`
}

// each annotation are associated with a sessionID, ImageID
type ImageAnnotation struct {
	Id           int64
	Image        int
	Person       int
	Lesion_ha    int `xorm:"default 0"`
	Lesion_ma    int `xorm:"default 0"`
	Lesion_sex   int `xorm:"default 0"`
	Lesion_hex   int `xorm:"default 0"`
	Lesion_va    int `xorm:"default 0"`
	Artifact     int
	Position     int
	Clear        int
	Level        int
	ArtifactType int
	Created      time.Time `xorm:"created"`
}

type Patient struct {
	Id                              int64
	Name                            string
	Id_card                         string `xorm:"unique"`
	Gender                          int
	Age                             int
	District                        string
	Community_health_service_center string
	Address                         string
	Mobie                           string
	Tel                             string
	EyeSightLefe                    string
	EyeSightRight                   string
}

type PersonToken struct {
	UserName string
	UserID   int64
	ExpireAt time.Time
	jwt.StandardClaims
}

func NewToken(person *Person) *PersonToken {
	claims := &PersonToken{
		person.Name,
		person.Id,
		time.Now().Add(time.Hour * 24 * 7),
		jwt.StandardClaims{
			Issuer: "Ling Dai",
		},
	}
	return claims
}

func ParseToken(tokenString string) (*PersonToken, error) {
	pt := &PersonToken{}
	token, err := jwt.ParseWithClaims(tokenString, pt, func(token *jwt.Token) (interface{}, error) {
		return []byte("Secret"), nil
	})

	if _, ok := token.Claims.(*PersonToken); ok && token.Valid {
		if pt.ExpireAt.Before(time.Now()) {
			return nil, errors.New("Data expired")
		}
	} else {
		levlog.E(err)
		return nil, err
	}
	return pt, nil
}

func (t *PersonToken) getToken() string {
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, t)
	ss, err := token.SignedString([]byte("Secret"))
	levlog.E(err)
	return ss
}

type App struct {
	engine *xorm.Engine
}

func NewApp() *App {
	return &App{}
}

var seededRand *rand.Rand = rand.New(
	rand.NewSource(time.Now().UnixNano()))

func randStringGen(length int) string {
	const charset = "abcdefghijklmnopqrstuvwxyz" +
		"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
	b := make([]byte, length)
	for i := range b {
		b[i] = charset[seededRand.Intn(len(charset))]
	}
	return string(b)
}

func (app *App) GetToken(name, psw string) (string, error) {
	person := Person{
		Name: name,
	}
	has, err := app.engine.Get(&person)
	if !has {
		levlog.Warning("No such user:", name)
		return "", errors.New("No such user:" + name)
	}
	if err != nil {
		levlog.E(err)
		return "", err
	}
	psw_check := PswHash(psw, person.Salt)
	if psw_check != person.Psw {
		// Error psw
		levlog.Info("Error Psw@", person.Name, ":", psw_check, "$", person.Psw)
		return "", errors.New("Error Password")
	}
	return NewToken(&person).getToken(), nil
}

func (app *App) HandleToken(j *JsonType) (*JsonType, int) {
	payload := j.GetJson("req_json")
	username := payload.GetString("user_name")
	if username == "" {
		return ResponseError(400, "No user name")
	}
	levlog.Trace("Gen token for ", username)
	psw := payload.GetString("password")
	t, err := app.GetToken(username, psw)
	if err != nil {
		return ResponseError(400, err.Error())
	}
	result := NewJson()
	result.Set("token", t)
	return ResponseJson(result)
}

func (app *App) RequireToken(j *JsonType) (*JsonType, int) {
	token := j.GetString("req_token")
	if token == "" {
		return ResponseError(401, "No Token")
	}
	per, err := ParseToken(token)
	levlog.E(err)
	if err != nil || per.UserName == "" {
		return ResponseError(401, "Token Error")
	}
	j.Set("token_username", per.UserName)
	j.Set("token_userid", fmt.Sprint(per.UserID))
	return NoResponse()
}

func (app *App) RequireUserInfo(j *JsonType) (*JsonType, int) {
	userId := j.GetInt("token_userid")
	if userId <= 0 {
		return ResponseError(401, "Error")
	}
	person := Person{
		Id: int64(userId),
	}
	if succ, err := app.engine.Get(&person); succ == false || err != nil {
		levlog.E(err)
		return ResponseError(500, "DB error")
	}
	j.Set("user_info", person)
	levlog.Trace(person)
	return ResponseSucc()
}

func (app *App) PreProcessImg(img image.Image) image.Image {
	levlog.Info("Drawing")
	dc := gg.NewContextForImage(img)
	size := img.Bounds().Size()
	dc.DrawLine(
		float64(size.X)/2.0,
		0,
		float64(size.X)/2.0,
		float64(size.Y))
	dc.DrawLine(
		0,
		float64(size.Y)/2.0,
		float64(size.X),
		float64(size.Y)/2.0,
	)
	dc.SetLineWidth(2)
	dc.SetRGBA(0, 1, 0, 1)
	dc.Stroke()
	if err := dc.LoadFontFace("resource/DejaVuSans.ttf", 96); err != nil {
		panic(err)
	}

	dc.SetRGB(1, 1, 1)
	dc.DrawStringAnchored("1", 100, 100, 0.5, 0.5)
	dc.DrawStringAnchored("2", float64(size.X)-100, 100, 0.5, 0.5)
	dc.DrawStringAnchored("3", 100, float64(size.Y)-100, 0.5, 0.5)
	dc.DrawStringAnchored("4", float64(size.X)-100, float64(size.Y)-100, 0.5, 0.5)
	return dc.Image()
}

func (app *App) readImg(path string) string {
	file, err := os.Open(path)
	levlog.E(err)
	state, err := file.Stat()
	levlog.E(err)
	defer file.Close()
	levlog.E(err)
	img, format, err := image.Decode(file)
	levlog.E(err)
	levlog.Info("Read image format ", format)
	img = app.PreProcessImg(img)
	buffer := bytes.NewBuffer(make([]byte, 0))
	err = jpeg.Encode(buffer, img, &jpeg.Options{Quality: 50})
	levlog.E(err)
	imgBase64Str := base64.StdEncoding.EncodeToString(buffer.Bytes())
	img2html := fmt.Sprintf("data:image/%s;base64,", format) + imgBase64Str
	levlog.Debug("Encode Size: ", buffer.Len(), " Original size:", state.Size(), " html Size:", len(img2html))
	return img2html
}

func (app *App) HandleImage(j *JsonType) (*JsonType, int) {
	imgName := "/data/" + j.GetString("req_url")
	levlog.Trace("Handling ", imgName)
	return ResponseRaw(app.readImg(imgName))
}

func readInt(key string, r *http.Request) int {
	result, err := strconv.Atoi(r.Header.Get(key))
	levlog.E(err)
	return result
}

func (app *App) HandleImageList(j *JsonType) (*JsonType, int) {
	jsonType := j.GetJson("req_json")
	id := j.GetInt("token_userid")
	offset := jsonType.GetInt("offset")
	limit := jsonType.GetInt("limit")
	permission := j.GetJson("user_info").GetInt("Permission")
	//levlog.Trace(jsonType.GetJson("user_info"))
	levlog.Trace("permission is ", permission)
	retv := NewJson()
	l, err := app.getNextImgList(id, permission, offset, limit)
	if err != nil {
		levlog.E(err)
		return ResponseError(500, "DB Error")
	}
	retv.Set("Sessions", l)
	return ResponseJson(retv)
}

func (app *App) HandleAddAnnotation(jsonType *JsonType) (*JsonType, int) {
	levlog.Info("Add Annotation")
	anno := ImageAnnotation{}
	err := jsonType.UnmarshalBody(&anno)
	if err != nil {
		levlog.E(err)
		return ResponseError(500, err.Error())
	}
	levlog.Info(anno.Position)
	anno.Person = jsonType.GetInt("token_userid")
	if res, err := app.engine.Insert(&anno); err != nil || res != 1 {
		levlog.E(err)
		if res != 1 {
			levlog.E(errors.New("Affected rows not 1"))
		}
		return ResponseError(500, "Database Error")
	}
	return NoResponse()
}

func (app *App) HandleAddImage(jsonType *JsonType) (*JsonType, int) {
	personid := jsonType.GetInt("token_userid")
	if personid != 1 {
		return ResponseError(401, "Permission denied")
	}
	data := Image{}
	err := jsonType.UnmarshalBody(&data)
	if err != nil {
		levlog.E(err)
		return ResponseError(500, "Error")
	}
	err = app.AddImage(&data)
	if err != nil {
		levlog.E(err)
		return ResponseError(500, err.Error())
	}
	return ResponseAnyToJson(data)
}

func (app *App) HandleGetAnnotationList(jsonType *JsonType) (*JsonType, int) {
	personid := jsonType.GetInt("token_userid")
	var data []ImageAnnotation
	err := app.engine.Where("person=?", personid).Desc("id").Find(&data)
	if err != nil {
		levlog.Error(err)
		return ResponseError(500, "Database Error")
	}
	jsonData, err := json.Marshal(data)
	if err != nil {
		levlog.E(err)
		return ResponseError(500, "JsonParse Error")
	}
	return ResponseRaw(string(jsonData))
}

func (app *App) HandleAddPatient(j *JsonType) (*JsonType, int) {
	patient := Patient{}
	j.UnmarshalBody(&patient)
	_, err := app.engine.Insert(&patient)
	levlog.E(err)
	return ResponseAnyToJson(patient)
}

func (app *App) HandleAddSession(j *JsonType) (*JsonType, int) {
	session := AnnoSession{}
	levlog.E(j.UnmarshalBody(&session))
	_, err := app.engine.Insert(&session)
	levlog.E(err)
	return ResponseAnyToJson(session)
}

func (app *App) HandleGetSession(j *JsonType) (*JsonType, int) {
	session := AnnoSession{
		Id: int64(j.GetJson("req_json").GetInt("Id")),
	}
	levlog.Info("Get session:", session)
	_, err := app.engine.Cols("id").Get(&session)
	levlog.E(err)
	images := make([]Image, 0)
	app.engine.SQL(
		`SELECT *FROM image WHERE id in
				(SELECT image FROM image_in_session WHERE session=?)`,
		session.Id,
	).Find(&images)
	session.Images = images
	for index := range session.Images {
		session.Images[index].Data = app.readImg("/data/" + session.Images[index].Path)
	}
	succ, err := app.engine.SQL("SELECT gender,age,eye_sight_lefe,eye_sight_right from patient WHERE id= ?",
		images[0].Patient,
	).Get(&session.PatientInfo)
	levlog.Info(succ)
	levlog.E(err)
	return ResponseAnyToJson(session)
}

func (app *App) HandleAddImageToSession(j *JsonType) (*JsonType, int) {
	imgrec := ImageInSession{}
	levlog.E(j.UnmarshalBody(&imgrec))
	app.engine.Insert(&imgrec)
	return ResponseAnyToJson(imgrec)
}
